#include "config.h"

#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <openssl/md5.h>
#include <openssl/aes.h>
#include <dirent.h>

#define DBG_SUBSYS S_LIBCLUSTER

#include "nodectl.h"
#include "dbg.h"

#define OPT_CONFIG_PREFIX  "/opt/fusionstack/data"
#define OPT_NODECTL_PREFIX "/dev/shm/lich4/nodectl"

#define NODECTL_FILE_MAGIC 0x54321

int __nodectl_get(nodectl_file_t *nf, char *value, const char *_default)
{
        int ret;

        YASSERT(nf->magic == NODECTL_FILE_MAGIC);

        ret = _get_text(nf->path, value, MAX_BUF_LEN);
        if (ret < 0) {
                ret = -ret;
                ret = _set_text(nf->path, _default, strlen(_default) + 1, O_CREAT | O_TRUNC);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                strcpy(value, _default);
        }

        return 0;
err_ret:
        return ret;
}

int __nodectl_get_int(nodectl_file_t *nf, int _default)
{
        int ret;
        char value[MAX_BUF_LEN], defvalue[MAX_BUF_LEN];

        YASSERT(nf->magic == NODECTL_FILE_MAGIC);

        sprintf(defvalue, "%d", _default);

        ret = nf->get(nf, value, defvalue);
        if(unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        return atoi(value);
err_ret:
        return _default;
}

uint64_t __nodectl_get_long(nodectl_file_t *nf, uint64_t _default)
{
        int ret;
        char value[MAX_BUF_LEN], defvalue[MAX_BUF_LEN];

        YASSERT(nf->magic == NODECTL_FILE_MAGIC);

        sprintf(defvalue, "%ju", _default);

        ret = nf->get(nf, value, defvalue);
        if(unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        return atoll(value);
err_ret:
        return _default;
}

int __nodectl_set(nodectl_file_t *nf, const char *value)
{
        int ret;

        YASSERT(nf->magic == NODECTL_FILE_MAGIC);

        ret = _set_text(nf->path, value, strlen(value) + 1, O_CREAT | O_TRUNC);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int __nodectl_set2(nodectl_file_t *nf, const char *value)
{
        int ret, fd;

        YASSERT(nf->magic == NODECTL_FILE_MAGIC);

        ret = path_validate(nf->path, 0, YLIB_DIRCREATE);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        fd = open(nf->path, O_CREAT | O_WRONLY, 0644);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = _write(fd, value, strlen(value) + 1);
        if (ret < 0) {
                ret = -ret;
                GOTO(err_fd, ret);
        }

        ret = fsync(fd);
        if (ret < 0) {
                ret = errno;
                GOTO(err_fd, ret);
        }

        close(fd);
        return 0;
err_fd:
        close(fd);
err_ret:
        return ret;
}

int __nodectl_register(nodectl_file_t *nf, const char  *_default, fnotify_callback mod_callback,
                     fnotify_callback del_callback, void *context)
{
        int ret;

        ret = nf->set(nf, _default);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = fnotify_register(nf->path, mod_callback, del_callback, context);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int __nodectl_unregister(nodectl_file_t *nf)
{
        int ret;

        YASSERT(nf->magic == NODECTL_FILE_MAGIC);

        ret = fnotify_unregister(nf->path);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

void __nodectl_unlink(nodectl_file_t *nf)
{
        YASSERT(nf->magic == NODECTL_FILE_MAGIC);

        if (_file_exist(nf->path) == 0) {
                _unlink(nf->path, "nodectl_unlink");
        }
}

int nodectl_file_init(nodectl_file_t *nf, const char *prefix, const char *key)
{
        snprintf(nf->path, MAX_PATH_LEN, "%s/%s", prefix, key);
        nf->magic = NODECTL_FILE_MAGIC;

        // ops
        nf->set = __nodectl_set;
        nf->set2 = __nodectl_set2;
        nf->get = __nodectl_get;
        nf->get_int = __nodectl_get_int;
        nf->get_long = __nodectl_get_long;

        nf->_register = __nodectl_register;
        nf->unregister = __nodectl_unregister;

        nf->unlink = __nodectl_unlink;

        return 0;
}

int opt_data_init(nodectl_file_t *nf, const char *key)
{
        return nodectl_file_init(nf, OPT_CONFIG_PREFIX, key);
}

int opt_nodectl_init(nodectl_file_t *nf, const char *key)
{
        return nodectl_file_init(nf, OPT_NODECTL_PREFIX, key);
}

static inline int __nodectl_option_mod(void *context, uint32_t mask);
static inline int __nodectl_option_reset(void *context, uint32_t mask);
static inline int __nodectl_option_set(nodectl_option_t *opt);

static inline int __nodectl_option_mod(void *context, uint32_t mask)
{
        int cv;
        nodectl_option_t *opt = context;
        nodectl_file_t *pnf = &opt->nf;

        cv = pnf->get_int(pnf, atoi(opt->dv));
        if (opt->_min != -1 && opt->_max != -1)
                itorange(&cv, opt->_min, opt->_max);

        opt->value = cv;

        DINFO("load %s dv %s value %d @[%d, %d] mask %u\n",
              pnf->path, opt->dv, opt->value, opt->_min, opt->_max, mask);

        if (opt->mod_cb) {
                opt->mod_cb(context, mask);
        }

        return 0;
}

static inline int __nodectl_option_reset(void *context, uint32_t mask)
{
        int ret;
        nodectl_option_t *opt = context;
        nodectl_file_t *pnf = &opt->nf;

        (void) mask;

        pnf->unregister(pnf);

        ret = __nodectl_option_set(opt);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        DINFO("reset %s dv %s value %d @[%d, %d] mask %u\n",
              pnf->path, opt->dv, opt->value, opt->_min, opt->_max, mask);

        return 0;
err_ret:
        return ret;
}

static inline int __nodectl_option_set(nodectl_option_t *opt)
{
        int ret, cv;
        char buf[MAX_NAME_LEN];
        nodectl_file_t *pnf = &opt->nf;

        cv = pnf->get_int(pnf, atoi(opt->dv));
        if (opt->_min != -1 && opt->_max != -1)
                itorange(&cv, opt->_min, opt->_max);

        sprintf(buf, "%d", cv);

        ret = pnf->_register(pnf, buf, __nodectl_option_mod, __nodectl_option_reset, opt);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        opt->value = cv;

        return 0;
err_ret:
        return ret;
}

static int __nodectl_option_start(nodectl_option_t *opt, const char *key, const char *dv, fnotify_callback mod_cb)
{
        int ret;

        ret = sy_rwlock_wrlock(&opt->lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (opt->started) {
                if (strcmp(opt->key, key) != 0) {
                        opt->nf.unregister(&opt->nf);
                        opt->started = 0;

                        DINFO("unregister %s\n", opt->nf.path);
                }
        }

        if (!opt->started) {
                nodectl_file_init(&opt->nf, opt->prefix, key);
                opt->mod_cb = mod_cb;

                strcpy(opt->key, key);
                strcpy(opt->dv, dv);
                opt->_min = -1;
                opt->_max = -1;

                ret = __nodectl_option_set(opt);
                if (unlikely(ret))
                        GOTO(err_lock, ret);

                DINFO("register %s dv %s value %d @[%d, %d]\n",
                      opt->nf.path, opt->dv, opt->value, opt->_min, opt->_max);

                // TODO multi thread
                opt->started = 1;
        }

        sy_rwlock_unlock(&opt->lock);
        return 0;
err_lock:
        sy_rwlock_unlock(&opt->lock);
err_ret:
        return ret;
}

int nodectl_option_init(nodectl_option_t *opt, const char *prefix)
{
        int ret;

        memset(opt, 0x0, sizeof(nodectl_option_t));

        ret = sy_rwlock_init(&opt->lock, "nodectl_opt");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        opt->started = 0;
        strcpy(opt->prefix, prefix);

        opt->start = __nodectl_option_start;

        return 0;
err_ret:
        return ret;
}

int opt_data_init2(nodectl_option_t *opt)
{
        return nodectl_option_init(opt, OPT_CONFIG_PREFIX);
}

int opt_nodectl_init2(nodectl_option_t *opt)
{
        return nodectl_option_init(opt, OPT_NODECTL_PREFIX);
}

int opt_config_register(const char *key, const char  *_default, fnotify_callback mod_callback,
                        fnotify_callback del_callback, void *context)
{
        int ret;

        nodectl_file_t nf, *pnf = &nf;
        opt_data_init(pnf, key);

        ret = pnf->set(pnf, _default);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = fnotify_register(pnf->path, mod_callback, del_callback, context);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}
